﻿using Devonline.AspNetCore.Security;
using Devonline.Caching;
using Devonline.Security;
using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

public static class ServiceExtensions
{
    #region application builder extensions
    /// <summary>
    /// 使用 TSetting 类型获取配置文件 Application Setting 节点配置项, 并以单例形式添加到服务容器并返回
    /// </summary>
    /// <typeparam name="TSetting">Application Setting 配置项的类型名称</typeparam>
    /// <param name="builder">WebApplicationBuilder 实例</param>
    /// <param name="section">自定义设置节点名称, 默认配置节点: AppSetting</param>
    /// <param name="setup">在注册到依赖注入容器之前的处理方法</param>
    /// <returns></returns>
    public static TSetting AddSetting<TSetting>(this WebApplicationBuilder builder, string? section = default, Action<TSetting>? setup = default) where TSetting : class, new()
    {
        var setting = (string.IsNullOrWhiteSpace(section) || section == nameof(AppSetting)) ? builder.Configuration.GetSetting<TSetting>() : builder.Configuration.GetSetting<TSetting>(section) ?? new TSetting();
        setup?.Invoke(setting);

        if (setting is HttpSetting httpSetting)
        {
            httpSetting.IdentityDbContext ??= builder.Configuration.GetConnectionString(nameof(httpSetting.IdentityDbContext));
        }

        builder.Services.AddSetting(setting);
        return setting;
    }
    /// <summary>
    /// 配置服务器信息
    /// </summary>
    /// <param name="builder">web host builder</param>
    /// <param name="httpSetting">http setting</param>
    /// <returns></returns>
    public static IWebHostBuilder ConfigureKestrel(this IWebHostBuilder builder, HttpSetting httpSetting)
    {
        return builder.ConfigureKestrel(options => options.Limits.MaxRequestBodySize = httpSetting.Attachment.Upload.Total);
    }
    /// <summary>
    /// 启用全局请求中间件
    /// </summary>
    /// <param name="builder">web host builder</param>
    /// <param name="httpSetting">http setting</param>
    /// <returns></returns>
    public static IApplicationBuilder UseGlobalRequest(this IApplicationBuilder builder, HttpSetting httpSetting)
    {
        builder.UseMiddleware<GlobalRequestMiddleware>();
        return builder;
    }
    /// <summary>
    /// 注入 Http 拦截器服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">Http 配置项的实例</param>
    /// <returns></returns>
    public static IServiceCollection AddHttpIntercept(this IServiceCollection services, HttpSetting httpSetting)
    {
        if (httpSetting.EnableIntercept)
        {
            services.AddSingleton(HttpInterceptor.Instance);
        }

        return services;
    }
    /// <summary>
    /// 启用 Http 请求拦截中间件
    /// </summary>
    /// <param name="builder">web host builder</param>
    /// <param name="httpSetting">http setting</param>
    /// <returns></returns>
    public static IApplicationBuilder UseHttpIntercept(this IApplicationBuilder builder, HttpSetting httpSetting)
    {
        if (httpSetting.EnableIntercept)
        {
            builder.UseMiddleware<HttpInterceptMiddleware>();
        }

        return builder;
    }

    /// <summary>
    /// 注册 HTTP 客户端服务
    /// </summary>
    /// <typeparam name="THttpClientService">HttpClient 服务类型</typeparam>
    /// <typeparam name="TEndpoint">连接的终结点类型</typeparam>
    /// <param name="services">依赖注入容器</param>
    /// <param name="endpoint">连接的终结点</param>
    /// <returns></returns>
    public static IServiceCollection AddHttpClientService<THttpClientService, TEndpoint>(this IServiceCollection services, TEndpoint endpoint) where THttpClientService : class where TEndpoint : class, IEndpoint
    {
        services.AddHttpClient<THttpClientService>(endpoint.Name, httpClient =>
        {
            ArgumentNullException.ThrowIfNull(endpoint.Host);
            httpClient.BaseAddress = new Uri(endpoint.Host);
            httpClient.DefaultRequestHeaders.Add(Microsoft.Net.Http.Headers.HeaderNames.Accept, ContentType.Json);

            if (endpoint is IAuthEndpoint authEndpoint && authEndpoint.AuthType != AuthType.None)
            {
                switch (authEndpoint.AuthType)
                {
                    case AuthType.Token:
                        if (!string.IsNullOrWhiteSpace(authEndpoint.Token))
                        {
                            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authEndpoint.Token);
                        }
                        break;
                    case AuthType.Basic:
                        if (!string.IsNullOrWhiteSpace(authEndpoint.UserName) && !string.IsNullOrWhiteSpace(authEndpoint.Password))
                        {
                            var credentials = System.Text.Encoding.ASCII.GetBytes($"{authEndpoint.UserName}:{authEndpoint.Password}");
                            httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(credentials));
                        }
                        break;
                    default:
                        break;
                }
            }
        });

        services.AddSingleton(endpoint);
        services.AddSingleton<THttpClientService>();
        return services;
    }
    /// <summary>
    /// 根据 TEndpoint 添加默认的 HTTP 远程接口调用服务
    /// 基础 HTTP 接口服务不对返回值做处理, 仅完成通用调用和记录日志任务
    /// 如需解构返回值数据结构, 请参见使用 HttpResult<T> 数据结构解构返回值的 HttpClientResultService<TEndpoint>  服务
    /// 或根据实际业务编写子类继承 HttpClientService<TEndpoint> 并重写 ResponseAdapter<TResponseModel> 方法来实现
    /// </summary>
    /// <param name="services"></param>
    /// <param name="endpoint"></param>
    /// <returns></returns>
    public static IServiceCollection AddHttpClientService<TEndpoint>(this IServiceCollection services, TEndpoint endpoint) where TEndpoint : class, IEndpoint => services.AddHttpClientService<HttpClientService<TEndpoint>, TEndpoint>(endpoint);
    /// <summary>
    /// 根据 TEndpoint 添加默认的使用 HttpResult<T> 数据结构解构返回值的 HTTP 远程接口调用服务
    /// </summary>
    /// <param name="services"></param>
    /// <param name="endpoint"></param>
    /// <returns></returns>
    public static IServiceCollection AddHttpClientResultService<TEndpoint>(this IServiceCollection services, TEndpoint endpoint) where TEndpoint : class, IEndpoint => services.AddHttpClientService<HttpClientResultService<TEndpoint>, TEndpoint>(endpoint);
    #endregion

    #region service extensions
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <typeparam name="TSetting">Application Setting 配置项的类型名称</typeparam>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="setting">Application Setting 配置项的实例</param>
    /// <returns></returns>
    public static IServiceCollection AddSetting<TSetting>(this IServiceCollection services, TSetting setting) where TSetting : class, new()
    {
        services.AddSingleton(setting);
        if (typeof(TSetting) != typeof(AppSetting) && setting is AppSetting appSetting)
        {
            services.AddSingleton(appSetting);
            if (appSetting is HttpSetting httpSetting)
            {
                services.AddSingleton(httpSetting);
            }
        }

        return services;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的实体数据对象模型数据操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddDataService(this IServiceCollection services)
    {
        services.AddScoped<IFileService, FileService>();
        services.AddScoped(typeof(IDataService<,>), typeof(DataService<,>));
        services.AddScoped(typeof(IAttachmentService<>), typeof(AttachmentService<>));
        services.AddScoped(typeof(IDataWithAttachmentService<,>), typeof(DataWithAttachmentService<,>));
        services.AddScoped(typeof(IDataWithCollectionService<,,>), typeof(DataWithCollectionService<,,>));
        services.AddScoped(typeof(IDataWithCollectionAndAttachmentService<,,>), typeof(DataWithCollectionAndAttachmentService<,,>));
        return services;
    }
    /// <summary>
    /// 添加图片处理相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddImageService(this IServiceCollection services)
    {
        services.AddScoped<IImageResizeService, ImageResizeService>();
        services.AddScoped<IImageWatermarkService, ImageWatermarkService>();
        services.AddScoped<IImageInformationService, ImageInformationService>();
        services.AddScoped<IImageService, ImageService>();
        return services;
    }
    /// <summary>
    /// 注册默认的基于字符串主键的 Excel 导入导出操作相关服务
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddExcelService(this IServiceCollection services)
    {
        services.AddTransient<IExcelExportService, ExcelExportService>();
        services.AddTransient<IExcelImportService, ExcelImportService>();
        return services;
    }

    /// <summary>
    /// 注册默认 MVC 控制器
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IMvcBuilder AddControllers(this IServiceCollection services, HttpSetting httpSetting) => services.AddControllers(options => options.Filters.Add<ExceptionFilter>()).AddJsonOptions(httpSetting);
    /// <summary>
    /// 注册默认 MVC 控制器及页面
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IMvcBuilder AddControllersWithViews(this IServiceCollection services, HttpSetting httpSetting) => services.AddControllersWithViews(options => options.Filters.Add<ExceptionFilter>()).AddJsonOptions(httpSetting);

    /// <summary>
    /// 注册通用服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultServices(this IServiceCollection services, HttpSetting httpSetting)
    {
        services.AddLogging();
        services.AddHttpContextAccessor();
        services.AddResponseCompression(x => x.EnableForHttps = true);
        services.AddDataService();
        services.AddImageService();
        services.AddExcelService();
        services.AddHybridCache(httpSetting.Cache);
        services.AddHttpIntercept(httpSetting);
        services.AddScoped<LanguageService>();

        if (httpSetting.Endpoint.Mode == EndpointMode.Distributed && (httpSetting.Endpoint.MachineCode != UNIT_ZERO || httpSetting.Endpoint.DataCenterCode != UNIT_ZERO))
        {
            //分布式配置
            KeyGenerator.Configure(options =>
            {
                options.MachineCode = httpSetting.Endpoint.MachineCode;
                options.DataCenterCode = httpSetting.Endpoint.DataCenterCode;
            });
        }

        if (httpSetting.HttpLogging is not null)
        {
            services.AddHttpLogging(options => options = httpSetting.HttpLogging);
        }

        if (httpSetting.W3CLogger is not null)
        {
            services.AddW3CLogging(options => options = httpSetting.W3CLogger);
        }

        if (httpSetting.ResourcesPath is not null)
        {
            services.AddLocalization(options => options.ResourcesPath = httpSetting.ResourcesPath);
        }

        if (httpSetting.CaptchaExpireTime > UNIT_ZERO)
        {
            services.AddCaptchaService();
        }

        if (httpSetting.AuthType == AuthType.Basic || httpSetting.AuthType == AuthType.Cookie || httpSetting.AuthType == AuthType.Oidc)
        {
            services.AddDefaultSecurityPolicy(httpSetting);
        }

        services.Configure<ApiBehaviorOptions>(config => config.SuppressModelStateInvalidFilter = true);
        return services;
    }
    /// <summary>
    /// 添加验证码服务
    /// </summary>
    /// <param name="services"></param>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public static IServiceCollection AddCaptchaService(this IServiceCollection services, IConfiguration? configuration = default)
    {
        if (configuration is null)
        {
            services.AddCaptcha();
        }
        else
        {
            services.AddCaptcha(configuration);
        }

        services.AddScoped<ICaptcha, RandomCaptcha>();
        services.AddScoped<ICaptchaService, CaptchaService>();
        return services;
    }

    /// <summary>
    /// 添加默认的数据安全服务
    /// </summary>
    /// <param name="services"></param>
    /// <param name="dataSecuritySetting"></param>
    /// <returns></returns>
    public static IServiceCollection AddDataSecurityService<TDbContext>(this IServiceCollection services, DataSecuritySetting dataSecuritySetting) where TDbContext : BaseSecurityDbContext
    {
        services.AddSecurityService(dataSecuritySetting);
        services.AddScoped<IDataSecurityService<TDbContext>, DataSecurityService<TDbContext>>();
        return services;
    }
    /// <summary>
    /// 添加默认的数据安全服务
    /// </summary>
    /// <param name="services"></param>
    /// <param name="dataSecuritySetting"></param>
    /// <returns></returns>
    public static IServiceCollection AddUserDataSecurityService<TDbContext>(this IServiceCollection services, DataSecuritySetting dataSecuritySetting) where TDbContext : BaseSecurityDbContext
    {
        services.AddSecurityService(dataSecuritySetting);
        services.AddScoped<IUserDataSecurityService<TDbContext>, UserDataSecurityService<TDbContext>>();
        return services;
    }

    /// <summary>
    /// 数据保护功能待验证
    /// </summary>
    /// <typeparam name="TDbContext">数据保护存储密钥的数据库上下文</typeparam>
    /// <param name="services">依赖注入容器</param>
    /// <param name="dataProtection">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDataProtection<TDbContext>(this IServiceCollection services, DataProtectionSetting dataProtection) where TDbContext : DbContext, IDataProtectionKeyContext
    {
        if (!string.IsNullOrWhiteSpace(dataProtection.ApplicationDiscriminator))
        {
            services.AddDataProtection(x => x.ApplicationDiscriminator = dataProtection.ApplicationDiscriminator)
               .SetApplicationName(dataProtection.ApplicationDiscriminator)
               .PersistKeysToDbContext<TDbContext>();
        }

        return services;
    }
    #endregion

    #region 认证和授权
    /// <summary>
    /// 注册安全策略
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    public static IServiceCollection AddDefaultSecurityPolicy(this IServiceCollection services, HttpSetting httpSetting)
    {
        //启用跨域策略
        if (!string.IsNullOrWhiteSpace(httpSetting.CorsOrigins))
        {
            services.AddCors(options => options.AddPolicy(DEFAULT_CORS_POLICY, policy => policy.WithOrigins(httpSetting.CorsOrigins.Split(CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).AllowAnyHeader().AllowAnyMethod()));
        }

        //启用重定向和 Cookie 策略
        if (httpSetting.ProtocolType == ProtocolType.Https && httpSetting.HttpFordwarded)
        {
            services.AddCookiePolicy(options =>
            {
                options.MinimumSameSitePolicy = httpSetting.SameSiteMode;
                options.OnAppendCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions);
                options.OnDeleteCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions);
            });
        }

        return services;
    }
    /// <summary>
    /// 默认使用 Cookie 的认证服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthentication(this IServiceCollection services, HttpSetting httpSetting)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
        .AddCookie(options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromMinutes(httpSetting.CookieExpireTime);
            var userInteraction = httpSetting.UserInteraction;
            if (userInteraction is not null)
            {
                options.LoginPath = userInteraction.Login;
                options.LogoutPath = userInteraction.Logout;
                options.AccessDeniedPath = userInteraction.AccessDenied;
                options.ReturnUrlParameter = userInteraction.ReturnUrl;
            }
        });
        return services;
    }
    /// <summary>
    /// 注册默认的 OpenIdConnect 类型的认证服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="openIdConnectOptions">认证配置项</param>
    /// <returns></returns>
    public static IServiceCollection AddOidcAuthentication(this IServiceCollection services, OpenIdConnectOptions openIdConnectOptions)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddOpenIdConnect(options => options = openIdConnectOptions);
        //.AddOpenIdConnect(options =>
        //{
        //    //options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        //    //options.ResponseType = OpenIdConnectResponseType.Code;
        //    //options.SaveTokens = true;
        //    //options.GetClaimsFromUserInfoEndpoint = true;
        //    //options.MapInboundClaims = false;
        //    //options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
        //    //options.TokenValidationParameters.RoleClaimType = "roles";

        //    ArgumentNullException.ThrowIfNull(openIdConnectOptions.ResponseType);
        //    ArgumentNullException.ThrowIfNull(openIdConnectOptions.Scope);
        //    options.Authority = openIdConnectOptions.Authority;
        //    options.ClientId = openIdConnectOptions.ClientId;
        //    options.ClientSecret = openIdConnectOptions.ClientSecret;
        //    //options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        //    //options.ResponseType = identitySetting.ResponseType;
        //    //options.SaveTokens = identitySetting.SaveTokens;
        //    //options.GetClaimsFromUserInfoEndpoint = identitySetting.GetClaimsFromUserInfoEndpoint;
        //    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        //    options.ResponseType = OpenIdConnectResponseType.Code;
        //    options.SaveTokens = true;
        //    options.GetClaimsFromUserInfoEndpoint = true;
        //    options.MapInboundClaims = false;

        //    options.Scope.Clear();
        //    options.Scope.Add("openid");
        //    options.Scope.Add("profile");
        //    options.Scope.Add("email");
        //    options.Scope.Add("offline_access");

        //    options.ClaimActions.Remove("amr");
        //    options.ClaimActions.MapUniqueJsonKey("website", "website");

        //    // .NET 9 feature
        //    //options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Require;
        //    options.TokenValidationParameters.NameClaimType = "name";
        //    options.TokenValidationParameters.RoleClaimType = "role";

        //    //options.ResponseMode = OpenIdConnectResponseMode.FormPost;
        //    //options.Scope.AddRange(identitySetting.Scope.Split(CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
        //    //options.RequireHttpsMetadata = identitySetting.RequireHttpsMetadata;
        //    //options.SignedOutRedirectUri = "/Home/Index";

        //    //options.CorrelationCookie.Expiration = TimeSpan.FromMinutes(5);
        //    //options.NonceCookie.Expiration = TimeSpan.FromMinutes(5);
        //    //if (httpSetting.ProtocolType == ProtocolType.Https)
        //    //{
        //    //    options.CorrelationCookie.SameSite = SameSiteMode.Lax;
        //    //    options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
        //    //    options.NonceCookie.SameSite = SameSiteMode.Lax;
        //    //    options.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
        //    //}
        //    //else
        //    //{
        //    //    options.CorrelationCookie.HttpOnly = true;
        //    //    options.NonceCookie.HttpOnly = true;
        //    //    options.RequireHttpsMetadata = false;
        //    //}
        //});

        return services;
    }
    /// <summary>
    /// 注册默认的授权服务
    /// </summary>
    /// <typeparam name="THandler">默认的授权处理程序</typeparam>
    /// <typeparam name="TRequirement">默认的需求处理程序</typeparam>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="authenticationScheme">身份验证方案</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthorization<THandler, TRequirement>(this IServiceCollection services, string authenticationScheme) where THandler : class, IAuthorizationHandler where TRequirement : class, IAuthorizationRequirement, new()
    {
        services.AddTransient<IAuthorizationHandler, THandler>();
        services.AddTransient<IAuthorizationRequirement, TRequirement>();
        services.AddAuthorization(options =>
        {
            var policy = new AuthorizationPolicyBuilder(authenticationScheme).RequireAuthenticatedUser().AddRequirements(new TRequirement()).Build();
            options.AddPolicy(DEFAULT_AUTHORIZATION_POLICY, policy);
            options.DefaultPolicy = policy;
            options.FallbackPolicy = policy;
        });
        return services;
    }
    /// <summary>
    /// 注册默认的授权服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="authenticationScheme">身份验证方案</param>
    /// <param name="requirements">身份验证需求</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthorization(this IServiceCollection services, string authenticationScheme, params IAuthorizationRequirement[] requirements)
    {
        services.AddAuthorization(options =>
        {
            var policy = new AuthorizationPolicyBuilder(authenticationScheme).RequireAuthenticatedUser().AddRequirements(requirements).Build();
            options.AddPolicy(DEFAULT_AUTHORIZATION_POLICY, policy);
            options.DefaultPolicy = policy;
            options.FallbackPolicy = policy;
        });
        return services;
    }
    /// <summary>
    /// 注册默认的授权服务
    /// </summary>
    /// <param name="services">依赖注入容器服务</param>
    /// <param name="authenticationScheme">身份验证方案</param>
    /// <param name="requirements">身份验证需求</param>
    /// <param name="claimType">设定的声明类型</param>
    /// <param name="allowedValues">允许的声明值</param>
    /// <returns></returns>
    public static IServiceCollection AddDefaultAuthorization(this IServiceCollection services, string authenticationScheme, IAuthorizationRequirement[] requirements, string claimType, params string[] allowedValues)
    {
        services.AddAuthorization(options =>
        {
            var policy = new AuthorizationPolicyBuilder(authenticationScheme).RequireAuthenticatedUser().AddRequirements(requirements).RequireClaim(claimType, allowedValues).Build();
            options.AddPolicy(DEFAULT_AUTHORIZATION_POLICY, policy);
            options.DefaultPolicy = policy;
            options.FallbackPolicy = policy;
        });
        return services;
    }
    #endregion

    #region IApplicationBuilder extensions
    /// <summary>
    /// 启动安全策略
    /// </summary>
    /// <param name="app">IApplicationBuilder</param>
    /// <param name="httpSetting">配置项</param>
    /// <returns></returns>
    public static IApplicationBuilder UseDefaultSecurityPolicy(this IApplicationBuilder app, HttpSetting httpSetting)
    {
        //启用跨域策略
        if (!string.IsNullOrWhiteSpace(httpSetting.CorsOrigins))
        {
            app.UseCors(DEFAULT_CORS_POLICY);
        }

        //启动重定向和 Cookie 策略
        if (httpSetting.ProtocolType == ProtocolType.Https && httpSetting.HttpFordwarded)
        {
            //使用 webserver header 配置进行 http 重定向
            var fordwardedHeaderOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto };
            fordwardedHeaderOptions.KnownNetworks.Clear();
            fordwardedHeaderOptions.KnownProxies.Clear();
            app.UseForwardedHeaders(fordwardedHeaderOptions);

            app.UseCookiePolicy(new CookiePolicyOptions
            {
                MinimumSameSitePolicy = httpSetting.SameSiteMode,
                OnAppendCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions),
                OnDeleteCookie = cookieContext => SetSameSite(cookieContext.Context, cookieContext.CookieOptions)
            });
        }

        return app;
    }
    /// <summary>
    /// 全局默认异常处理方法
    /// 在 WebApplication 下会自动记录异常日志
    /// </summary>
    /// <param name="app">IApplicationBuilder</param>
    /// <param name="action">Exception Handler</param>
    /// <returns></returns>
    public static IApplicationBuilder UseException(this IApplicationBuilder app, Action<HttpContext, Exception?>? action = default) => app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        ExceptionHandler = async context =>
        {
            var message = "服务器内部错误";
            var logger = (app as WebApplication)?.Logger;
            var exception = context.Features.Get<IExceptionHandlerFeature>();
            if (exception != null && exception.Error != null)
            {
                var errorMessage = exception.Error.GetMessage();
                switch (exception.Error)
                {
                    case BadHttpRequestException:
                        //业务类型的错误需要抛出 BadHttpRequestException 异常, 一律按 BadRequest(400) 处理
                        context.Response.StatusCode = StatusCodes.Status400BadRequest;
                        message = exception.Error.Message;
                        logger?.LogInformation(message);
                        break;
                    case UnauthorizedAccessException:
                        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                        message = "用户尚未登陆, 请先登录!";
                        break;
                    case DbUpdateException:
                        context.Response.StatusCode = StatusCodes.Status409Conflict;
                        message = "数据异常, 请联系管理员处理!";
                        logger?.LogWarning(exception.Error, exception.Error.GetMessage());
                        break;
                    default:
                        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                        logger?.LogError(exception.Error, exception.Error.GetMessage());
                        break;
                }
#if !DEBUG
                message = errorMessage;
#endif
            }

            //context.Response.ContentType = ContentType.Json;
            action?.Invoke(context, exception?.Error);
            await context.Response.WriteAsync(message).ConfigureAwait(false);
        }
    });
    /// <summary>
    /// 启动默认 IApplicationBuilder
    /// </summary>
    /// <param name="app">WebApplication</param>
    /// <param name="httpSetting">配置项</param>
    /// <param name="beforeAuth">在启用认证与授权之前执行的方法委托</param>
    /// <returns></returns>
    public static IApplicationBuilder UseDefaultBuilder(this WebApplication app, HttpSetting httpSetting, Action? beforeAuth = default)
    {
        // Configure the HTTP request pipeline.
        if (app.Environment.IsProduction())
        {
            app.UseExceptionHandler("/Error");
        }
        else
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseException();
        if (httpSetting.HttpLogging is not null)
        {
            app.UseHttpLogging();
        }

        if (httpSetting.W3CLogger is not null)
        {
            app.UseW3CLogging();
        }

        if (httpSetting.ProtocolType == ProtocolType.Https)
        {
            app.UseHttpsRedirection();
        }

        if (httpSetting.UseStaticFiles)
        {
            app.UseStaticFiles();
        }

        app.UseRouting();
        app.UseResponseCompression();
        app.UseHttpIntercept(httpSetting);
        app.UseGlobalRequest(httpSetting);
        app.UseDefaultSecurityPolicy(httpSetting);

        beforeAuth?.Invoke();
        if (httpSetting.EnableAuth)
        {
            app.UseAuthentication();
            app.UseAuthorization();
            app.MapControllers().RequireAuthorization(DEFAULT_AUTHORIZATION_POLICY);
            app.MapDefaultControllerRoute().RequireAuthorization(DEFAULT_AUTHORIZATION_POLICY);
            app.MapControllerRoute(name: "api", pattern: "api/{controller=Home}/{action=Index}/{id?}").RequireAuthorization(DEFAULT_AUTHORIZATION_POLICY);
        }
        else
        {
            app.MapControllers();
            app.MapDefaultControllerRoute();
            app.MapControllerRoute(name: "api", pattern: "api/{controller=Home}/{action=Index}/{id?}");
        }

        return app;
    }
    #endregion

    #region 内部方法
    /// <summary>
    /// 设置 Cookie 策略
    /// </summary>
    /// <param name="httpContext"></param>
    /// <param name="options"></param>
    private static void SetSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None && DisallowsSameSiteNone(httpContext.Request.Headers.UserAgent.ToString()))
        {
            options.SameSite = SameSiteMode.Lax;
        }
    }
    /// <summary>
    /// Checks if the UserAgent is known to interpret an unknown value as Strict.
    /// For those the <see cref="CookieOptions.SameSite" /> property should be
    /// set to <see cref="Unspecified" />.
    /// </summary>
    /// <remarks>
    /// This code is taken from Microsoft:
    /// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
    /// </remarks>
    /// <param name="userAgent">The user agent string to check.</param>
    /// <returns>Whether the specified user agent (browser) accepts SameSite=None or not.</returns>
    private static bool DisallowsSameSiteNone(string userAgent)
    {
        // Cover all iOS based browsers here. This includes:
        //   - Safari on iOS 12 for iPhone, iPod Touch, iPad
        //   - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
        //   - Chrome on iOS 12 for iPhone, iPod Touch, iPad
        // All of which are broken by SameSite=None, because they use the
        // iOS networking stack.
        // Notes from Thinktecture:
        // Regarding https://caniuse.com/#search=samesite iOS versions lower
        // than 12 are not supporting SameSite at all. Starting with version 13
        // unknown values are NOT treated as strict anymore. Therefore we only
        // need to check version 12.
        if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
        {
            return true;
        }

        // Cover Mac OS X based browsers that use the Mac OS networking stack.
        // This includes:
        //   - Safari on Mac OS X.
        // This does not include:
        //   - Chrome on Mac OS X
        // because they do not use the Mac OS networking stack.
        // Notes from Thinktecture:
        // Regarding https://caniuse.com/#search=samesite MacOS X versions lower
        // than 10.14 are not supporting SameSite at all. Starting with version
        // 10.15 unknown values are NOT treated as strict anymore. Therefore we
        // only need to check version 10.14.
        if (userAgent.Contains("Safari") && userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && userAgent.Contains("Version/"))
        {
            return true;
        }

        // Cover Chrome 50-69, because some versions are broken by SameSite=None
        // and none in this range require it.
        // Note: this covers some pre-Chromium Edge versions,
        // but pre-Chromium Edge does not require SameSite=None.
        // Notes from Thinktecture:
        // We can not validate this assumption, but we trust Microsofts
        // evaluation. And overall not sending a SameSite value equals to the same
        // behavior as SameSite=None for these old versions anyways.
        if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
        {
            return true;
        }

        return false;
    }
    #endregion
}